﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.ListBox
{
  internal class SelectionManager
  {
    #region Constructors

    internal SelectionManager( ListBox listBox )
    {
      if( listBox == null )
        throw new ArgumentNullException( "listBox" );

      m_listBox = listBox;
    }

    #endregion

    #region SelectionMode Property

    internal SelectionMode SelectionMode
    {
      get
      {
        return m_selectionMode;
      }
      set
      {
        if( m_selectionMode != value )
        {
          m_selectionMode = value;
          this.OnSelectionModeChanged();
        }
      }
    }

    private SelectionMode m_selectionMode = SelectionMode.Single;

    private void OnSelectionModeChanged()
    {
      using( this.DeferNotifySelectionChanged() )
      {
        this.UnselectAll( true );
      }
    }

    #endregion

    #region IsUpdatingSelection Property

    internal bool IsUpdatingSelection
    {
      get
      {
        return ( m_isUpdatingSelection > 0 );
      }
    }

    private int m_isUpdatingSelection;

    private IDisposable DeferNotifySelectionChanged()
    {
      return new DeferNotifySelectionChangedDisposable( this );
    }

    #endregion

    #region StartAnchor Private Property

    private AnchorPath StartAnchor
    {
      get
      {
        return m_startAnchor;
      }
    }

    private readonly AnchorPath m_startAnchor = new AnchorPath();

    #endregion

    #region UpdateSelectionOnNextMouseUp Private Property

    private SelectionFlag UpdateSelectionOnNextMouseUp
    {
      get
      {
        return m_mouseUpSelection;
      }
    }

    private readonly SelectionFlag m_mouseUpSelection = new SelectionFlag();

    #endregion

    #region DataSource Private Property

    private DataSourceWrapper DataSource
    {
      get
      {
        return m_listBox.DataSourceWrapper;
      }
    }

    #endregion

    #region DataHelper Private Property

    private IDataSourceWrapperHelper DataHelper
    {
      get
      {
        return this.DataSource.DataHelper;
      }
    }

    #endregion

    #region SelectionChanged Event

    internal event EventHandler SelectionChanged;

    private void RaiseSelectionChanged()
    {
      if( this.SelectionChanged != null )
      {
        this.SelectionChanged( this, EventArgs.Empty );
      }
    }

    #endregion

    #region UpdateSelectionProperties Event

    internal event EventHandler UpdateSelectionProperties;

    private void RaiseUpdateSelectionProperties()
    {
      if( this.UpdateSelectionProperties != null )
      {
        this.UpdateSelectionProperties( this, EventArgs.Empty );
      }
    }

    #endregion

    // This method is use to update the SelectedRanges when SelectedItem is modified directly
    internal void UpdateSingleSelectionHelper( ListBoxContainer container, DataPath dataPath )
    {
      using( this.DeferNotifySelectionChanged() )
      {
        this.SelectJustThisItem( container, dataPath );
      }
    }

    internal void UpdateSelectionHelper( ListBoxContainer container, SelectionAction action )
    {
      using( this.DeferNotifySelectionChanged() )
      {
        switch( this.SelectionMode )
        {
          case SelectionMode.Extended:
            this.UpdateExtendedSelection( container, action );
            break;

          case SelectionMode.Multiple:
            this.UpdateMultipleSelection( container, action );
            break;

          case SelectionMode.Single:
            this.UpdateSingleSelection( container, action );
            break;

          default:
            throw new ListBoxInternalException( "Unknown SelectionMode" );
        }
      }
    }

    internal void SelectAllHelper()
    {
      using( this.DeferNotifySelectionChanged() )
      {
        // Not available in Single selection
        if( this.SelectionMode == SelectionMode.Single )
          return;

        SelectionRange selectAll = SelectionRange.All;
        IList<SelectionRange> selectedRanges = m_listBox.SelectedRanges;

        for( int i = selectedRanges.Count - 1; i >= 0; i-- )
        {
          SelectionRange range = selectedRanges[ i ];

          // An unselection range was found before a select all.  Some item
          // may not be selected.
          if( range.SelectionType != SelectionType.Selection )
            break;

          // All items are already selected.
          if( object.Equals( selectAll, range ) )
            return;
        }

        selectedRanges.Clear();
        selectedRanges.Add( selectAll );

        //Raise Internal even to resynchronize all SelectedRanges on layout elements.
        this.RaiseUpdateSelectionProperties();

        //Raise the event to notify user of changes in the Selection.
        this.RaiseSelectionChanged();
      }
    }

    private void UpdateExtendedSelection( ListBoxContainer container, SelectionAction action )
    {
      if( action != SelectionAction.MouseUp )
      {
        this.UpdateSelectionOnNextMouseUp.Clear();
      }

      switch( action )
      {
        case SelectionAction.CodeBehind:
          this.UpdateExtendedSelectionForCodeBehind( container );
          break;

        case SelectionAction.Navigation:
          this.UpdateExtendedSelectionForNavigation( container );
          break;

        case SelectionAction.MouseDown:
          this.UpdateExtendedSelectionForMouseDown( container );
          break;

        case SelectionAction.MouseUp:
          this.UpdateExtendedSelectionForMouseUp( container );
          break;

        case SelectionAction.Keyboard:
          this.UpdateExtendedSelectionForKeyboard( container );
          break;

        default:
          break;
      }
    }

    private void UpdateExtendedSelectionForCodeBehind( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelectable ) )
      {
        this.ToggleItemSelection( container );
      }
    }

    private void UpdateExtendedSelectionForNavigation( ListBoxContainer container )
    {
      bool isCtrlDown = ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control;
      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

      if( isCtrlDown )
      {
        if( isShiftDown )
        {
          this.DoRangeSelection( container );
        }
        else
        {
          Key lastNavigationKey = m_listBox.Panel.LastNavigationKey;

          if( ( lastNavigationKey == Key.PageUp )
            || ( lastNavigationKey == Key.PageDown )
            || ( lastNavigationKey == Key.End )
            || ( lastNavigationKey == Key.Home ) )
          {
            if( ( container == null ) || !container.IsSelectable )
            {
              this.UnselectAll();
            }
            else
            {
              this.SelectJustThisItem( container, container.DataPath, false );
              this.StartAnchor.Set( SelectionType.Selection );
            }
          }
        }
      }
      else if( isShiftDown )
      {
        if( !container.IsSelectable )
          return;

        // We need to unselect every previous selection before processing
        // since Ctrl key is not down.
        // Do not notify for this UnSelectAll since a
        // SelectionChanged will be raised by DoRangeSelection.
        this.UnselectAll( false );

        this.StartAnchor.Set( SelectionType.Selection );
        this.DoRangeSelection( container );
      }
      else
      {
        if( ( container == null ) || !container.IsSelectable )
        {
          this.StartAnchor.Clear();
          this.UnselectAll();
        }
        else
        {
          this.StartAnchor.Set( container.DataPath, SelectionType.Selection );
          this.SelectJustThisItem( container, container.DataPath );
        }
      }
    }

    private void UpdateExtendedSelectionForMouseDown( ListBoxContainer container )
    {
      bool isCtrlDown = ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control;
      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

      if( isCtrlDown )
      {
        if( isShiftDown )
        {
          this.DoRangeSelection( container );
          // Do not update the StartAnchor with the last container DataPath
          // This is the same behavior as MS ListBox
        }
        else
        {
          //If the row is already selected, I want to update selection status only on the MouseUp, this is to allow Drag/Drop operations.
          //If the row is not already selected, we update selection on MouseDown to allow Drag/Drop operations.
          // this.Select return false when the item is already selected.
          if( ( container != null ) && ( container.IsSelectable ) )
          {
            if( container.IsSelected )
            {
              this.StartAnchor.Set( container.DataPath, SelectionType.Unselection );
              this.UpdateSelectionOnNextMouseUp.Target = container;
            }
            else
            {
              this.StartAnchor.Set( container.DataPath, SelectionType.Selection );
              this.SelectJustThisItem( container, container.DataPath, false );
            }
          }
          else
          {
            this.StartAnchor.Clear();
          }
        }
      }
      else if( isShiftDown )
      {
        if( !container.IsSelectable )
          return;

        // We need to unselect every previous selection before processing
        // since Ctrl key is not down.
        // Do not notify for this UnSelectAll since a
        // SelectionChanged will be raised by DoRangeSelection.
        this.UnselectAll( false );

        this.StartAnchor.Set( SelectionType.Selection );
        this.DoRangeSelection( container );
      }
      else
      {
        if( ( container == null ) || ( !container.IsSelectable ) )
        {
          this.StartAnchor.Clear();
          this.UnselectAll();
        }
        else if( container.IsSelected )
        {
          this.StartAnchor.Set( container.DataPath, SelectionType.Unselection );
          this.UpdateSelectionOnNextMouseUp.Target = container;
        }
        else
        {
          this.StartAnchor.Set( container.DataPath, SelectionType.Selection );
          this.SelectJustThisItem( container, container.DataPath );
        }
      }
    }

    private void UpdateExtendedSelectionForMouseUp( ListBoxContainer container )
    {
      SelectionFlag updateSelectionOnNextMouseUp = this.UpdateSelectionOnNextMouseUp;

      //Only process MouseUp if the same ListBoxContainer has MouseUp
      bool isMouseUpMatch = ( updateSelectionOnNextMouseUp.UpdateSelection )
                         && ( updateSelectionOnNextMouseUp.Target == container );

      if( isMouseUpMatch )
      {
        if( ( container != null ) && ( container.IsSelectable ) )
        {
          bool isCtrlDown = ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control;
          bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

          if( isCtrlDown )
          {
            if( isShiftDown )
            {
              this.DoRangeSelection( container );
            }
            else
            {
              this.ToggleItemSelection( container );
            }
          }
          else
          {
            this.SelectJustThisItem( container, container.DataPath );
          }
        }
      }

      // We reset the m_updateSelectionOnNextMouseUp to be sure when no focus is moved 
      // around that mouse up will toggle selection
      updateSelectionOnNextMouseUp.Clear();
    }

    private void UpdateExtendedSelectionForKeyboard( ListBoxContainer container )
    {
      this.UpdateSelectionOnNextMouseUp.Target = container;

      bool isCtrlDown = ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control;
      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;

      if( isShiftDown )
      {
        if( isCtrlDown )
        {
          this.DoRangeSelection( container );
        }
        else
        {
          if( !container.IsSelectable )
            return;

          // We need to unselect every previous selection before processing
          // since Ctrl key is not down.
          // Do not notify for this UnSelectAll since a
          // SelectionChanged will be raised by DoRangeSelection.
          this.UnselectAll( false );

          this.StartAnchor.Set( SelectionType.Selection );
          this.DoRangeSelection( container );
        }
      }
      else if( ( container != null ) && ( container.IsSelectable ) )
      {
        this.ToggleItemSelection( container );
        this.StartAnchor.Set( container.DataPath, SelectionManager.GetSelectionType( !container.IsSelected ) );
      }
    }

    private void UpdateMultipleSelection( ListBoxContainer container, SelectionAction action )
    {
      //Multiple Selection mode does not support key modifiers for range selection such as CTRL or SHIFT

      if( action != SelectionAction.MouseUp )
      {
        this.UpdateSelectionOnNextMouseUp.Clear();
      }

      switch( action )
      {
        case SelectionAction.CodeBehind:
          this.UpdateMultipleSelectionForCodeBehind( container );
          break;

        case SelectionAction.MouseDown:
          this.UpdateMultipleSelectionForMouseDown( container );
          break;

        case SelectionAction.MouseUp:
          this.UpdateMultipleSelectionForMouseUp( container );
          break;

        case SelectionAction.Keyboard:
          this.UpdateMultipleSelectionForKeyboard( container );
          break;
      }
    }

    private void UpdateMultipleSelectionForCodeBehind( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelectable ) )
      {
        this.ToggleItemSelection( container );
      }
    }

    private void UpdateMultipleSelectionForMouseDown( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelectable ) )
      {
        //If the row is already selected, I want to update selection status only on the MouseUp, this is to allow Drag/Drop operations.
        //If the row is not already selected, we update selection on MouseDown to allow Drag/Drop operations.
        // this.Select return false when the item is already selected.
        if( container.IsSelected )
        {
          this.UpdateSelectionOnNextMouseUp.Target = container;
        }
        else
        {
          this.SelectJustThisItem( container, container.DataPath, false );
        }
      }
    }

    private void UpdateMultipleSelectionForMouseUp( ListBoxContainer container )
    {
      SelectionFlag updateSelectionOnNextMouseUp = this.UpdateSelectionOnNextMouseUp;

      //Only process MouseUp if the same ListBoxContainer has MouseUp.
      bool isMouseUpMatch = ( updateSelectionOnNextMouseUp.UpdateSelection )
                         && ( container == updateSelectionOnNextMouseUp.Target );

      if( isMouseUpMatch )
      {
        if( ( container != null ) && ( container.IsSelectable ) ) //Condition is placed there, but is redundant with the one in the MouseDown handling.
        {
          this.ToggleItemSelection( container );
        }
      }

      updateSelectionOnNextMouseUp.Clear();
    }

    private void UpdateMultipleSelectionForKeyboard( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelectable ) )
      {
        this.ToggleItemSelection( container );
      }
    }

    private void UpdateSingleSelection( ListBoxContainer container, SelectionAction action )
    {
      if( action != SelectionAction.MouseUp )
      {
        this.UpdateSelectionOnNextMouseUp.Clear();
      }

      bool doSelection = false;

      bool isCtrlDown = ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control;

      //Keyboard Navigation.
      if( action == SelectionAction.Navigation )
      {
        if( !isCtrlDown )
        {
          //If Control Key is held, that means we shall move currency without moving selection.
          //Absence of CTRL modifier means we move selection, hence doSelection = true.
          doSelection = true;
        }
        else //this means that CTRL was pressed
        {
          Key lastNavigationKey = m_listBox.Panel.LastNavigationKey;

          //CTRL-Home, CTRL-End, CTRL-PageUp and CTRL-PageDown are specific navigation shortcuts for which the above mentionned limitation
          //does not apply, hence setting the doSelection at True.
          if( ( lastNavigationKey == Key.PageUp )
            || ( lastNavigationKey == Key.PageDown )
            || ( lastNavigationKey == Key.End )
            || ( lastNavigationKey == Key.Home ) )
          {
            doSelection = true;
          }
        }
      }
      else if( action == SelectionAction.MouseUp )
      {
        // The selection is updated in MouseDown for SelectionMode.Single
        return;
      }
      else //Mouse, Direct Keyboard Selection (Space), or ...
      {
        doSelection = true;
      }

      if( !doSelection )
        return;

      if( ( container != null ) && ( container.IsSelectable ) )
      {
        // If Ctrl is down, we want to toggle the selection, else we just select this
        // item if it is not already selected
        if( ( isCtrlDown ) || ( action == SelectionAction.CodeBehind ) )
        {
          if( !container.IsSelected )
          {
            this.SelectJustThisItem( container, container.DataPath );
          }
          else
          {
            this.UnselectAll();
          }
        }
        else
        {
          this.SelectJustThisItem( container, container.DataPath );
        }
      }
      else
      {
        this.UnselectAll();
      }
    }

    internal void UnselectAllHelper()
    {
      using( this.DeferNotifySelectionChanged() )
      {
        this.UnselectAll( true );
      }
    }

    private void UnselectAll()
    {
      this.UnselectAll( true );
    }

    private void UnselectAll( bool notify )
    {
      bool changesDone = false;

      if( m_listBox.SelectedRanges.Count > 0 )
      {
        m_listBox.SelectedRanges.Clear();
        changesDone = true;
      }

      if( changesDone )
      {
        this.RaiseUpdateSelectionProperties();
      }

      // Only raise SelectionChanged if changes were done
      if( notify && changesDone )
      {
        this.RaiseSelectionChanged();
      }
    }

    internal void NotifySelectionChanged()
    {
      using( this.DeferNotifySelectionChanged() )
      {
        //Raise Internal event to resynchronize all SelectedRanges on layout elements.
        this.RaiseUpdateSelectionProperties();

        //Raise the event to notify user of changes in the Selection.
        this.RaiseSelectionChanged();
      }
    }

    private void SelectJustThisItem( ListBoxContainer container, DataPath dataPath )
    {
      this.SelectJustThisItem( container, dataPath, true );
    }

    private void SelectJustThisItem( ListBoxContainer container, DataPath dataPath, bool unselect )
    {
      IList<SelectionRange> selectedRanges = m_listBox.SelectedRanges;
      SelectionRange singleSelectionRange = null;

      if( unselect )
      {
        if( selectedRanges.Count == 1 )
        {
          singleSelectionRange = selectedRanges.Single();
        }

        this.UnselectAll( false );
      }

      SelectionRange newRange = SelectionManager.SelectionRangeHelper(
        this.DataHelper,
        dataPath,
        true,
        dataPath,
        true,
        SelectionType.Selection );

      selectedRanges.Add( newRange );

      if( container != null )
      {
        container.SelectionRange = newRange;
        container.SetIsSelectedInternal( true );
      }

      // The selection has changed only if the final result is different.
      bool selectionChanged = ( singleSelectionRange == null )
                           || ( !object.Equals( singleSelectionRange, newRange ) );

      if( selectionChanged )
      {
        this.RaiseSelectionChanged();
      }
    }

    private void ToggleItemSelection( ListBoxContainer container )
    {
      SelectionRange currentRange = container.SelectionRange;

      if( ( currentRange != null )
        && ( currentRange.IsSingleItem )
        && ( m_listBox.SelectedRanges.Any() )
        && ( m_listBox.SelectedRanges.Last() == currentRange ) )
      {
        Debug.Assert( container.IsSelected == ( currentRange.SelectionType == SelectionType.Selection ), "Inconsistant SelectionRange." );

        SelectionRange newRange = new SelectionRange( currentRange );
        newRange.SelectionType = SelectionManager.ToggleSelectionType( currentRange.SelectionType );

        m_listBox.SelectedRanges[ m_listBox.SelectedRanges.Count - 1 ] = newRange;
      }
      else
      {
        DataPath dataPath = container.DataPath;
        SelectionRange newRange = SelectionManager.SelectionRangeHelper(
                                    this.DataHelper,
                                    dataPath,
                                    true,
                                    dataPath,
                                    true,
                                    SelectionManager.GetSelectionType( container.IsSelected ) );

        m_listBox.SelectedRanges.Add( newRange );

        container.SelectionRange = newRange;
      }

      container.SetIsSelectedInternal( !container.IsSelected );

      //Raise Internal even to resynchronize all SelectedRanges on layout elements.
      this.RaiseUpdateSelectionProperties();

      //Raise the public event for the user of the component.
      this.RaiseSelectionChanged();
    }

    private void DoRangeSelection( ListBoxContainer container )
    {
      if( container == null )
        throw new ArgumentNullException( "container cannot be null." );

      if( !container.IsSelectable )
        return;

      //No start anchor defined, define it and "single select" the element passed.
      if( !this.StartAnchor.IsSet )
      {
        this.UpdateStartAnchorPath( container );
        this.SelectJustThisItem( container, container.DataPath, false );
        return;
      }

      DataPath startPath = this.StartAnchor.Path;
      DataPath endPath = container.DataPath;

      if( this.Compare( startPath, endPath ) > 0 )
      {
        //This means the end is "smaller" than the start. To have consistent Data, it is better than the End is always after the Start.
        SelectionManager.Swap( ref startPath, ref endPath );
      }

      SelectionRange selectionRange = SelectionManager.SelectionRangeHelper(
                                        this.DataHelper,
                                        startPath,
                                        true,
                                        endPath,
                                        true,
                                        this.StartAnchor.Type );
      m_listBox.SelectedRanges.Add( selectionRange );

      //Raise Internal even to resynchronize all SelectedRanges on layout elements.
      this.RaiseUpdateSelectionProperties();

      //Raise the event to notify user of changes in the Selection.
      this.RaiseSelectionChanged();
    }

    private static SelectionType GetSelectionType( bool unselect )
    {
      if( unselect )
        return SelectionType.Unselection;

      return SelectionType.Selection;
    }

    private void UpdateStartAnchorPath( ListBoxContainer container )
    {
      if( ( container != null ) && ( container.IsSelectable ) )
      {
        SelectionType selectionType = SelectionManager.GetSelectionType( container.IsSelected );
        this.StartAnchor.Set( container.DataPath, selectionType );
      }
      else
      {
        this.StartAnchor.Clear();
      }
    }

    internal static SelectionRange SelectionRangeHelper( IDataSourceWrapperHelper dataHelper,
      DataPath rangeStart,
      bool startInclusive,
      DataPath rangeEnd,
      bool endInclusive,
      SelectionType selectionType )
    {
      if( rangeStart.Depth != rangeEnd.Depth )
        throw new InvalidOperationException( "rangeStart is not consistent with rangeEnd. DataPaths do not have same size." );

      object startingItem = dataHelper.ConvertBack( rangeStart );
      object endingItem = dataHelper.ConvertBack( rangeEnd );

      if( startingItem == null )
        throw new ArgumentException( "rangeStart contains invalid data. ", "rangeStart" );

      if( endingItem == null )
        throw new ArgumentException( "rangeEnd contains invalid data. ", "rangeEnd" );

      SelectionRange selectionRange;

      if( rangeStart == rangeEnd )
      {
        selectionRange = new SelectionRange( rangeStart );
      }
      else
      {
        selectionRange = new SelectionRange();
        selectionRange.StartRangeInfos.FromPath( rangeStart );
        selectionRange.EndRangeInfos.ToPath( rangeEnd );
      }

      selectionRange.SelectionType = selectionType;
      selectionRange.StartRangeInfos.IsInclusive = startInclusive;
      selectionRange.EndRangeInfos.IsInclusive = endInclusive;

      return selectionRange;
    }

    private static void Swap<T>( ref T x, ref T y )
    {
      T temp = x;
      x = y;
      y = temp;
    }

    private static SelectionType ToggleSelectionType( SelectionType selectionType )
    {
      switch( selectionType )
      {
        case SelectionType.Selection:
          return SelectionType.Unselection;

        case SelectionType.Unselection:
          return SelectionType.Selection;

        default:
          throw new NotSupportedException();
      }
    }

    private int Compare( DataPath x, DataPath y )
    {
      return this.DataHelper.Compare( x, y );
    }

    #region Private Fields

    private readonly ListBox m_listBox; //null

    #endregion

    #region AnchorPath Private Class

    private sealed class AnchorPath
    {
      internal AnchorPath()
      {
      }

      public bool IsSet
      {
        get
        {
          return ( m_path != null );
        }
      }

      public DataPath Path
      {
        get
        {
          if( !this.IsSet )
            throw new InvalidOperationException();

          return m_path;
        }
      }

      public SelectionType Type
      {
        get
        {
          if( !this.IsSet )
            throw new InvalidOperationException();

          return m_selectionType;
        }
      }

      public void Set( DataPath path, SelectionType selectionType )
      {
        if( path == null )
          throw new ArgumentNullException( "path" );

        m_path = path;
        m_selectionType = selectionType;
      }

      public void Set( SelectionType selectionType )
      {
        m_selectionType = selectionType;
      }

      public void Clear()
      {
        m_path = null;
      }

      private DataPath m_path;
      private SelectionType m_selectionType;
    }

    #endregion

    #region SelectionFlag Private Class

    private sealed class SelectionFlag
    {
      internal SelectionFlag()
      {
      }

      public bool UpdateSelection
      {
        get
        {
          return m_updateSelection;
        }
      }

      public ListBoxContainer Target
      {
        get
        {
          if( ( !m_updateSelection ) || ( m_target == null ) )
            return null;

          return m_target.Target as ListBoxContainer;
        }
        set
        {
          if( value == null )
          {
            this.Clear();
          }
          else if( m_target == null )
          {
            m_target = new WeakReference( value );
            m_updateSelection = true;
          }
          else
          {
            ListBoxContainer container = m_target.Target as ListBoxContainer;
            if( container != value )
            {
              m_target = new WeakReference( value );
              m_updateSelection = true;
            }
          }
        }
      }

      public void Clear()
      {
        m_target = null;
        m_updateSelection = false;
      }

      private bool m_updateSelection;
      private WeakReference m_target; //null
    }

    #endregion

    #region DeferNotifySelectionChangedDisposable Private Class

    private class DeferNotifySelectionChangedDisposable : IDisposable
    {
      public DeferNotifySelectionChangedDisposable( SelectionManager manager )
      {
        if( manager == null )
          throw new ArgumentNullException( "manager" );

        m_manager = manager;
        m_manager.m_isUpdatingSelection++;
      }

      public void Dispose()
      {
        if( m_manager == null )
          return;

        m_manager.m_isUpdatingSelection--;
        m_manager = null;
      }

      private SelectionManager m_manager;
    }

    #endregion
  }
}
